#include <graphics/RenderSystem.h>
#include <system/Log.h>

namespace ne
{
    template<> RenderSystem* Singleton<RenderSystem>::mpInstance = 0;

    RenderSystem::RenderSystem()
        : mProjectionMatrix(Matrix4::IDENTITY),
          mViewMatrix(Matrix4::IDENTITY),
          mTextureMatrix(Matrix4::IDENTITY),
          mColorMatrix(Matrix4::IDENTITY),
          mClearColor(Color::BLACK),
          mColorWriteMask(CM_RGBA),
          mAlphaTestValue(0.0f),
          mAlphaTestFunc(CF_ALWAYS),
          mAlphaTestEnable(false),
          mClearDepth(1.0f),
          mDepthTestFunc(CF_LEQUAL),
          mDepthTestEnable(true),
          mDepthWriteEnable(true),
          mDepthNearRange(0.0f),
          mDepthFarRange(1.0f),
          mClearStencil(0),
          mStencilFrontTestValue(0),
          mStencilBackTestValue(0),
          mStencilFrontTestMask(0xFFFFFFFF),
          mStencilBackTestMask(0xFFFFFFFF),
          mStencilFrontTestFunc(CF_ALWAYS),
          mStencilBackTestFunc(CF_ALWAYS),
          mStencilFrontSrcFailOpt(SO_KEEP),
          mStencilFrontDstFailOpt(SO_KEEP),
          mStencilFrontDstPassOpt(SO_KEEP),
          mStencilBackSrcFailOpt(SO_KEEP),
          mStencilBackDstFailOpt(SO_KEEP),
          mStencilBackDstPassOpt(SO_KEEP),
          mStencilFrontWriteEnableMask(0xFFFFFFFF),
          mStencilBackWriteEnableMask(0xFFFFFFFF),
          mStencilTestEnable(false),
          mScissorLeft(0),
          mScissorBottom(0),
          mScissorWidth(0),
          mScissorHeight(0),
          mScissorTestEnable(false),
          mFrontFaceType(FFT_CCW),
          mCullFace(FT_BACK),
          mCullFaceEnable(true),
          mPolygonModeFront(PM_FILL),
          mPolygonModeBack(PM_FILL),
          mPolygonOffsetPointEnable(false),
          mPolygonOffsetLineEnable(false),
          mPolygonOffsetFillEnable(false),
          mPolygonOffsetFactor(0.0f),
          mPolygonOffsetUnits(0.0f),
          mBlendConstantColor(Color::ZERO),
          mBlendColorEquation(BE_ADD),
          mBlendAlphaEquation(BE_ADD),
          mBlendSrcColorFunc(BF_SRC_ALPHA),
          mBlendDstColorFunc(BF_ONE_MINUS_SRC_ALPHA),
          mBlendSrcAlphaFunc(BF_SRC_ALPHA),
          mBlendDstAlphaFunc(BF_ONE_MINUS_SRC_ALPHA),
          mBlendEnable(false),
          mLightEnable(false),
          mpFog(0),
          mFogEnable(false),
          mDitherEnable(true),
          mMultisampleEnable(false),
          mpViewport(0),
          mIsReady(false)
    {
    }

    bool RenderSystem::initialize()
    {
#if NE_RENDER_SYSTEM == NE_RENDER_SYSTEM_OPENGL
        GLenum glewError = glewInit();
        if (glewError != GLEW_OK)
        {
            Log::Out(LL_ERROR, "Initialize Glew error : [%s]", glewGetErrorString(glewError));
            return false;
        }
#endif

        Log::Out(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>");
#if NE_RENDER_SYSTEM == NE_RENDER_SYSTEM_OPENGL
        Log::Out("Glew Version: [%s]", glewGetString(GLEW_VERSION));
        Log::Out("OpenGL Vendor: [%s]", glGetString(GL_VENDOR));
        Log::Out("OpenGL Renderer: [%s]", glGetString(GL_RENDERER));
        Log::Out("OpenGL Version: [%s] is supported", glGetString(GL_VERSION));
        Log::Out("OpenGL Extensions: [%s] is supported", glGetString(GL_EXTENSIONS));
#else
        Log::Out("OpenGLES Vendor: [%s]", glGetString(GL_VENDOR));
        Log::Out("OpenGLES Renderer: [%s]", glGetString(GL_RENDERER));
        Log::Out("OpenGLES Version: [%s] is supported", glGetString(GL_VERSION));
        Log::Out("OpenGLES Extensions: [%s] is supported", glGetString(GL_EXTENSIONS));
#endif

#if NE_RENDER_SYSTEM == NE_RENDER_SYSTEM_OPENGL
    #ifndef GL_VERSION_2_1
        Log::Out(LL_ERROR, "Your device do not support OpenGL version 2.1.0");
        return false;
    #endif
#elif NE_RENDER_SYSTEM == NE_RENDER_SYSTEM_OPENGLES
    #ifndef GL_ES_VERSION_2_0
        Log::Out(LL_ERROR, "Your device do not support OpenGLES version 2.0");
        return false;
    #endif
#endif

        Log::Out("GLSL Version: [%s] is supported", glGetString(GL_SHADING_LANGUAGE_VERSION));
        Log::Out(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>");

        reset();

        mIsReady = true;

        return true;
    }

    bool RenderSystem::isReady() const
    {
        return mIsReady;
    }

    void RenderSystem::setRenderOption(const RenderOption &ro)
    {
        if (ro.colorWriteMask != getColorWriteMask())
        {
            setColorWriteMask(ro.colorWriteMask);
        }

        if (ro.depthTestEnable)
        {
            if (ro.depthTestEnable != isDepthTestEnable())
            {
                setDepthTestEnable(ro.depthTestEnable);
            }
            if (ro.depthTestFunc != getDepthTestFunc())
            {
                setDepthTestFunc(ro.depthTestFunc);
            }
        }
        else
        {
            if (ro.depthTestEnable != isDepthTestEnable())
            {
                setDepthTestEnable(ro.depthTestEnable);
            }
        }

        if (ro.depthWriteEnable != isDepthWriteEnable())
        {
            setDepthWriteEnable(ro.depthWriteEnable);
        }

        if (ro.cullFaceEnable)
        {
            if (ro.cullFaceEnable != isCullFaceEnable())
            {
                setCullFaceEnable(ro.cullFaceEnable);
            }
            if (ro.cullFace != getCullFaceType())
            {
                setCullFaceType(ro.cullFace);
            }
        }
        else
        {
            if (ro.cullFaceEnable != isCullFaceEnable())
            {
                setCullFaceEnable(ro.cullFaceEnable);
            }
        }

        setPolygonOffset(ro.polygonOffsetFactor, ro.polygonOffsetUnits);

        if (ro.blendEnable)
        {
            if (ro.blendEnable != isBlendEnable())
            {
                setBlendEnable(ro.blendEnable);
            }
            if (ro.blendColorEquation != getBlendEquation())
            {
                setBlendEquation(ro.blendColorEquation);
            }
            setBlendFunction(ro.blendSrcColorFunc, ro.blendDstColorFunc);
        }
        else
        {
            if (ro.blendEnable != isBlendEnable())
            {
                setBlendEnable(ro.blendEnable);
            }
        }
    }

    bool RenderSystem::render(const RenderPass &pass)
    {
        if (pass.material.notNull() &&
            pass.primitive.notNull() &&
            pass.primitive->getVertexCount())
        {
            if (pass.material->useProgram(true))
            {
                setRenderOption(pass.option);

                // TODO: Set Uniforms
                GLSLProgramPtr pProgram = pass.material->getProgram();
                if (pProgram.notNull())
                {
                    const Matrix4 &PM = getProjectionMatrix();
                    const Matrix4 &VM = getViewMatrix();
                    const Matrix4 &MM = pass.primitive->getTransformMatrix();
                    Matrix4 VPM = VM * PM;
                    Matrix4 MVPM = MM * VPM;
                    pProgram->setUniform("neProjectionMatrix", PM);
                    pProgram->setUniform("neViewMatrix", VM);
                    pProgram->setUniform("neModelMatrix", MM);
                    pProgram->setUniform("neViewProjectionMatrix", VPM);
                    pProgram->setUniform("neModelViewProjectionMatrix", MVPM);
                    //pProgram->validate();
                }

                // TODO: Bind Textures
                int locationTexture = 0;
                const TextureMap& textureMap = pass.material->getTextureMap();
                for (TextureMapConstIterator it=textureMap.begin(); it!=textureMap.end(); ++it)
                {
                    TexturePtr pTexture = it->second;
                    if (pTexture.notNull())
                    {
                        glActiveTexture(GL_TEXTURE0 + locationTexture);
                        glBindTexture(pTexture->getType(), pTexture->getSource());
                        glEnable(pTexture->getType());
                        ++locationTexture;
                    }
                }

                render(*(pass.primitive));

                for (TextureMapConstIterator it=textureMap.begin(); it!=textureMap.end(); ++it)
                {
                    TexturePtr pTexture = it->second;
                    if (pTexture.notNull())
                    {
                        glDisable(pTexture->getType());
                    }
                }
                glActiveTexture(GL_TEXTURE0);

                pass.material->useProgram(false);
                return true;
            }
        }
        return false;
    }

    bool RenderSystem::render(const PrimitiveDeclaration &pd)
    {
        if (pd.getVertexCount())
        {
            const VertexAttributeMap &vam = pd.getVertexAttributeMap();
            for (VertexAttributeMapConstIterator it=vam.begin(); it!=vam.end(); ++it)
            {
                VertexAttributePtr va = it->second;
                if (va.notNull())
                {
                    glBindBuffer(GL_ARRAY_BUFFER, va->getSource());
                    glVertexAttribPointer(va->getSemantic(), va->getGLDataCount(), va->getGLDataType(), va->isNormalized(), va->getStride(), va->getOffsetPointer());
                    glEnableVertexAttribArray(va->getSemantic());
                }
            }

            if (pd.hasIndexAttribute())
            {
                const VertexAttributeList &ial = pd.getIndexAttributeList();
                for (VertexAttributeListConstIterator it=ial.begin(); it!=ial.end(); ++it)
                {
                    VertexAttributePtr ia = *it;
                    if (ia.notNull())
                    {
                        glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ia->getSource());
                        glDrawElements(pd.getPrimitiveType(), ia->getCount(), ia->getGLDataType(), ia->getOffsetPointer());
                    }
                }
            }
            else
            {
                glDrawArrays(pd.getPrimitiveType(), 0, pd.getVertexCount());
            }

            for (VertexAttributeMapConstIterator it=vam.begin(); it!=vam.end(); ++it)
            {
                VertexAttributePtr va = it->second;
                if (va.notNull())
                {
                    glDisableVertexAttribArray(va->getSemantic());
                }
            }

            glBindBuffer(GL_ARRAY_BUFFER, 0);
            glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);

            return true;
        }
        return false;
    }

    void RenderSystem::flush()
    {
        glFlush();
    }

    void RenderSystem::finish()
    {
        glFinish();
    }

    void RenderSystem::reset()
    {
        setViewMatrix(Matrix4::IDENTITY);
        setProjectionMatrix(Matrix4::IDENTITY);
        setTextureMatrix(Matrix4::IDENTITY);
        setColorMatrix(Matrix4::IDENTITY);

        setClearColor(Color::BLACK);
        setColorWriteMask(CM_RGBA);

        setAlphaTestFunc(CF_ALWAYS, 0.0f);
        setAlphaTestEnable(false);

        setClearDepth(1.0f);
        setDepthTestFunc(CF_LEQUAL);
        setDepthTestEnable(true);
        setDepthWriteEnable(true);
        setDepthRange(0.0f, 1.0f);

        setClearStencil(0);
        setStencilTestFunc(CF_ALWAYS, 0, 0xFFFFFFFF);
        setStencilOperations(SO_KEEP, SO_KEEP, SO_KEEP);
        setStencilWriteEnableMask(0xFFFFFFFF);
        setStencilTestEnable(false);

        setScissor(0, 0, 0, 0);
        setScissorTestEnable(false);

        setFrontFaceType(FFT_CCW);
        setCullFaceType(FT_BACK);
        setCullFaceEnable(true);

        setPolygonMode(PM_FILL);
        setPolygonOffsetPointEnable(false);
        setPolygonOffsetLineEnable(false);
        setPolygonOffsetFillEnable(false);
        setPolygonOffset(0.0f, 0.0f);

        setBlendConstantColor(Color::ZERO);
        setBlendEquation(BE_ADD);
        setBlendFunction(BF_SRC_ALPHA, BF_ONE_MINUS_SRC_ALPHA);
        setBlendEnable(false);

        setLightEnable(false);
        setFogEnable(false);
        setDitherEnable(true);
        setMultisampleEnable(false);
    }

    void RenderSystem::checkError() const
    {
        GLenum error = glGetError();
        if (error != GL_NO_ERROR)
        {
            std::string errorString;
            switch (error)
            {
                case GL_NO_ERROR:
                {
                    errorString = "No error has been recorded. The value of this symbolic constant is guaranteed to be zero.";
                    break;
                }
                case GL_INVALID_ENUM:
                {
                    errorString = "An unacceptable value is specified for an enumerated argument. The offending command is ignored, having no side effect other than to set the error flag.";
                    break;
                }
                case GL_INVALID_VALUE:
                {
                    errorString = "A numeric argument is out of range. The offending command is ignored, having no side effect other than to set the error flag.";
                    break;
                }
                case GL_INVALID_OPERATION:
                {
                    errorString = "The specified operation is not allowed in the current state. The offending command is ignored, having no side effect other than to set the error flag.";
                    break;
                }
#if NE_RENDER_SYSTEM == NE_RENDER_SYSTEM_OPENGL
                case GL_STACK_OVERFLOW:
                {
                    errorString = "This command would cause a stack overflow. The offending command is ignored, having no side effect other than to set the error flag.";
                    break;
                }
                case GL_STACK_UNDERFLOW:
                {
                    errorString = "This command would cause a stack underflow. The offending command is ignored, having no side effect other than to set the error flag.";
                    break;
                }
#endif
                case GL_OUT_OF_MEMORY:
                {
                    errorString = "There is not enough memory left to execute the command. The state of the GL is undefined, except for the state of the error flags, after this error is recorded.";
                    break;
                }
                default:
                {
                    Log::Out(LL_ERROR, "Render System Invalid Error[0x%4X]", error);
                    errorString = "Invalid error code has been recorded.";
                    break;
                }
            }
            Log::Out(LL_ERROR, "Render System Error: [%s]", errorString.c_str());
        }
    }

    // Matrix
    void RenderSystem::setProjectionMatrix(const Matrix4 &m)
    {
        mProjectionMatrix = m;
#if NE_RENDER_SYSTEM == NE_RENDER_SYSTEM_OPENGL
        glMatrixMode(TMT_PROJECTION);
        glLoadMatrixf(mProjectionMatrix.ptr());
        glMatrixMode(TMT_VIEW);
#endif
    }

    const Matrix4& RenderSystem::getProjectionMatrix() const
    {
        return mProjectionMatrix;
    }

    void RenderSystem::setViewMatrix(const Matrix4 &m)
    {
        mViewMatrix = m;
#if NE_RENDER_SYSTEM == NE_RENDER_SYSTEM_OPENGL
        glMatrixMode(TMT_VIEW);
        glLoadMatrixf(mViewMatrix.ptr());
#endif
    }

    const Matrix4& RenderSystem::getViewMatrix() const
    {
        return mViewMatrix;
    }

    void RenderSystem::setTextureMatrix(const Matrix4 &m)
    {
        mTextureMatrix = m;
#if NE_RENDER_SYSTEM == NE_RENDER_SYSTEM_OPENGL
        glMatrixMode(TMT_TEXTURE);
        glLoadMatrixf(mTextureMatrix.ptr());
        glMatrixMode(TMT_VIEW);
#endif
    }

    const Matrix4& RenderSystem::getTextureMatrix() const
    {
        return mTextureMatrix;
    }

    void RenderSystem::setColorMatrix(const Matrix4 &m)
    {
        mColorMatrix = m;
#if NE_RENDER_SYSTEM == NE_RENDER_SYSTEM_OPENGL
        glMatrixMode(TMT_COLOR);
        glLoadMatrixf(mColorMatrix.ptr());
        glMatrixMode(TMT_VIEW);
#endif
    }

    const Matrix4& RenderSystem::getColorMatrix() const
    {
        return mColorMatrix;
    }

    // Clear Buffer Bit
    void RenderSystem::clear()
    {
        glClear(BBT_COLOR_BUFFER_BIT |
                BBT_DEPTH_BUFFER_BIT |
                BBT_STENCIL_BUFFER_BIT |
                BBT_ACCUM_BUFFER_BIT);
    }

    void RenderSystem::clear(const bool clearColor, const bool clearDepth, const bool clearStencil, const bool clearAccum)
    {
        uint32_t mask = 0;
        if (clearColor)
        {
            mask |= BBT_COLOR_BUFFER_BIT;
        }
        if (clearDepth)
        {
            mask |= BBT_DEPTH_BUFFER_BIT;
        }
        if (clearStencil)
        {
            mask |= BBT_STENCIL_BUFFER_BIT;
        }
        if (clearAccum)
        {
            mask |= BBT_ACCUM_BUFFER_BIT;
        }
        glClear(mask);
    }

    void RenderSystem::setClearColor(const Color &color)
    {
        mClearColor = color;
        glClearColor(mClearColor.r, mClearColor.g, mClearColor.b, mClearColor.a);
    }

    Color RenderSystem::getClearColor() const
    {
        return mClearColor;
    }

    void RenderSystem::setColorWriteMask(const byte mask)
    {
        mColorWriteMask = mask;
        bool colorWriteEnable[4];
        colorWriteEnable[0] = mColorWriteMask & CM_R;
        colorWriteEnable[1] = mColorWriteMask & CM_G;
        colorWriteEnable[2] = mColorWriteMask & CM_B;
        colorWriteEnable[3] = mColorWriteMask & CM_A;
        glColorMask(colorWriteEnable[0],
                    colorWriteEnable[1],
                    colorWriteEnable[2],
                    colorWriteEnable[3]);
    }

    byte RenderSystem::getColorWriteMask() const
    {
        return mColorWriteMask;
    }

    // Alpha Test
    void RenderSystem::setAlphaTestFunc(const CompareFunction f, const real a)
    {
        mAlphaTestFunc = f;
        mAlphaTestValue = a;
#if NE_RENDER_SYSTEM == NE_RENDER_SYSTEM_OPENGL
        glAlphaFunc(mAlphaTestFunc, mAlphaTestValue);
#endif
    }

    CompareFunction RenderSystem::getAlphaTestFunc() const
    {
        return mAlphaTestFunc;
    }

    real RenderSystem::getAlphaTestValue() const
    {
        return mAlphaTestValue;
    }

    void RenderSystem::setAlphaTestEnable(const bool b)
    {
        mAlphaTestEnable = b;
        if (mAlphaTestEnable)
        {
            glEnable(TT_ALPHA_TEST);
        }
        else
        {
            glDisable(TT_ALPHA_TEST);
        }
    }

    bool RenderSystem::isAlphaTestEnable() const
    {
        return mAlphaTestEnable;
    }

    // Depth Test
    void RenderSystem::setClearDepth(const real d)
    {
        mClearDepth = d;
#if NE_RENDER_SYSTEM == NE_RENDER_SYSTEM_OPENGL
        glClearDepth(mClearDepth);
#endif
    }

    real RenderSystem::getClearDepth() const
    {
        return mClearDepth;
    }

    void RenderSystem::setDepthTestFunc(const CompareFunction f)
    {
        mDepthTestFunc = f;
        glDepthFunc(mDepthTestFunc);
    }

    CompareFunction RenderSystem::getDepthTestFunc() const
    {
        return mDepthTestFunc;
    }

    void RenderSystem::setDepthTestEnable(const bool b)
    {
        mDepthTestEnable = b;
        if (mDepthTestEnable)
        {
            glEnable(TT_DEPTH_TEST);
        }
        else
        {
            glDisable(TT_DEPTH_TEST);
        }
    }

    bool RenderSystem::isDepthTestEnable() const
    {
        return mDepthTestEnable;
    }

    void RenderSystem::setDepthWriteEnable(const bool b)
    {
        mDepthWriteEnable = b;
        glDepthMask(mDepthWriteEnable);
    }

    bool RenderSystem::isDepthWriteEnable() const
    {
        return mDepthTestEnable;
    }

    void RenderSystem::setDepthRange(const real n, const real f)
    {
        mDepthNearRange = n;
        mDepthFarRange = f;
#if NE_RENDER_SYSTEM == NE_RENDER_SYSTEM_OPENGL
        glDepthRange(mDepthNearRange, mDepthFarRange);
#endif
    }

    real RenderSystem::getDepthNearRange() const
    {
        return mDepthNearRange;
    }

    real RenderSystem::getDepthFarRange() const
    {
        return mDepthFarRange;
    }

    // Stencil Test
    void RenderSystem::setClearStencil(const int a)
    {
        mClearStencil = a;
        glClearStencil(mClearStencil);
    }

    int RenderSystem::getClearStencil() const
    {
        return mClearStencil;
    }

    void RenderSystem::setStencilTestFunc(const CompareFunction f, const int value, const uint32_t mask)
    {
        mStencilFrontTestFunc = f;
        mStencilBackTestFunc = f;
        mStencilFrontTestValue = value;
        mStencilBackTestValue = value;
        mStencilFrontTestMask = mask;
        mStencilBackTestMask = mask;
        glStencilFunc(f, value, mask);
    }

    int RenderSystem::getStencilTestValue() const
    {
        return mStencilFrontTestValue;
    }

    uint32_t RenderSystem::getStencilTestMask() const
    {
        return mStencilFrontTestMask;
    }

    CompareFunction RenderSystem::getStencilTestFunc() const
    {
        return mStencilFrontTestFunc;
    }

    void RenderSystem::setStencilFrontTestFunc(const CompareFunction f, const int value, const uint32_t mask)
    {
        mStencilFrontTestFunc = f;
        mStencilFrontTestValue = value;
        mStencilFrontTestMask = mask;
        glStencilFuncSeparate(FT_FRONT, f, value, mask);
    }

    int RenderSystem::getStencilFrontTestValue() const
    {
        return mStencilFrontTestValue;
    }

    uint32_t RenderSystem::getStencilFrontTestMask() const
    {
        return mStencilFrontTestMask;
    }

    CompareFunction RenderSystem::getFrontStencilTestFunc() const
    {
        return mStencilFrontTestFunc;
    }

    void RenderSystem::setStencilBackTestFunc(const CompareFunction f, const int value, const uint32_t mask)
    {
        mStencilBackTestFunc = f;
        mStencilBackTestValue = value;
        mStencilBackTestMask = mask;
        glStencilFuncSeparate(FT_BACK, f, value, mask);
    }

    int RenderSystem::getStencilBackTestValue() const
    {
        return mStencilBackTestValue;
    }

    uint32_t RenderSystem::getStencilBackTestMask() const
    {
        return mStencilBackTestMask;
    }

    CompareFunction RenderSystem::getStencilBackTestFunc() const
    {
        return mStencilBackTestFunc;
    }

    void RenderSystem::setStencilOperations(const StencilOperation srcFailOpt, const StencilOperation dstFailOpt, const StencilOperation dstPassOpt)
    {
        mStencilFrontSrcFailOpt = srcFailOpt;
        mStencilBackSrcFailOpt = srcFailOpt;
        mStencilFrontDstFailOpt = dstFailOpt;
        mStencilBackDstFailOpt = dstFailOpt;
        mStencilFrontDstPassOpt = dstPassOpt;
        mStencilBackDstPassOpt = dstPassOpt;
        glStencilOp(srcFailOpt, dstFailOpt, dstPassOpt);
    }

    StencilOperation RenderSystem::getStencilSrcFailOperation() const
    {
        return mStencilFrontSrcFailOpt;
    }

    StencilOperation RenderSystem::getStencilDstFailOperation() const
    {
        return mStencilFrontDstFailOpt;
    }

    StencilOperation RenderSystem::getStencilDstPassOperation() const
    {
        return mStencilFrontDstPassOpt;
    }

    void RenderSystem::setStencilFrontOperations(const StencilOperation srcFailOpt, const StencilOperation dstFailOpt, const StencilOperation dstPassOpt)
    {
        mStencilFrontSrcFailOpt = srcFailOpt;
        mStencilFrontDstFailOpt = dstFailOpt;
        mStencilFrontDstPassOpt = dstPassOpt;
        glStencilOpSeparate(FT_FRONT, srcFailOpt, dstFailOpt, dstPassOpt);
    }

    StencilOperation RenderSystem::getStencilFrontSrcFailOperation() const
    {
        return mStencilFrontSrcFailOpt;
    }

    StencilOperation RenderSystem::getStencilFrontDstFailOperation() const
    {
        return mStencilFrontDstFailOpt;
    }

    StencilOperation RenderSystem::getStencilFrontDstPassOperation() const
    {
        return mStencilFrontDstPassOpt;
    }

    void RenderSystem::setStencilBackOperations(const StencilOperation srcFailOpt, const StencilOperation dstFailOpt, const StencilOperation dstPassOpt)
    {
        mStencilBackSrcFailOpt = srcFailOpt;
        mStencilBackDstFailOpt = dstFailOpt;
        mStencilBackDstPassOpt = dstPassOpt;
        glStencilOpSeparate(FT_BACK, srcFailOpt, dstFailOpt, dstPassOpt);
    }

    StencilOperation RenderSystem::getStencilBackSrcFailOperation() const
    {
        return mStencilBackSrcFailOpt;
    }

    StencilOperation RenderSystem::getStencilBackDstFailOperation() const
    {
        return mStencilBackDstFailOpt;
    }

    StencilOperation RenderSystem::getStencilBackDstPassOperation() const
    {
        return mStencilBackDstPassOpt;
    }

    void RenderSystem::setStencilWriteEnableMask(const uint32_t mask)
    {
        mStencilFrontWriteEnableMask = mask;
        mStencilBackWriteEnableMask = mask;
        glStencilMask(mask);
    }

    uint32_t RenderSystem::getStencilWriteEnableMask() const
    {
        return mStencilFrontWriteEnableMask;
    }

    void RenderSystem::setStencilFrontWriteEnableMask(const uint32_t mask)
    {
        mStencilFrontWriteEnableMask = mask;
        glStencilMaskSeparate(FT_FRONT, mask);
    }

    uint32_t RenderSystem::getStencilFrontWriteEnableMask() const
    {
        return mStencilFrontWriteEnableMask;
    }

    void RenderSystem::setStencilBackWriteEnableMask(const uint32_t mask)
    {
        mStencilBackWriteEnableMask = mask;
        glStencilMaskSeparate(FT_BACK, mask);
    }

    uint32_t RenderSystem::getStencilBackWriteEnableMask() const
    {
        return mStencilBackWriteEnableMask;
    }

    void RenderSystem::setStencilTestEnable(const bool b)
    {
        mStencilTestEnable = b;
        if (mStencilTestEnable)
        {
            glEnable(TT_STENCIL_TEST);
        }
        else
        {
            glDisable(TT_STENCIL_TEST);
        }
    }

    bool RenderSystem::isStencilTestEnable() const
    {
        return mStencilTestEnable;
    }

    // Scissor Test
    void RenderSystem::setScissor(const int left, const int bottom, const uint32_t width, const uint32_t height)
    {
        mScissorLeft = left;
        mScissorBottom = bottom;
        mScissorWidth = width;
        mScissorHeight = height;
        glScissor(left, bottom, width, height);
    }

    int RenderSystem::getScissorLeft() const
    {
        return mScissorLeft;
    }

    int RenderSystem::getScissorRight() const
    {
        return mScissorLeft + mScissorWidth;
    }

    int RenderSystem::getScissorTop() const
    {
        return mScissorBottom + mScissorHeight;
    }

    int RenderSystem::getScissorBottom() const
    {
        return mScissorBottom;
    }

    uint32_t RenderSystem::getScissorWidth() const
    {
        return mScissorWidth;
    }

    uint32_t RenderSystem::getScissorHeight() const
    {
        return mScissorHeight;
    }

    void RenderSystem::setScissorTestEnable(const bool b)
    {
        mScissorTestEnable = b;
        if (mScissorTestEnable)
        {
            glEnable(TT_SCISSOR_TEST);
        }
        else
        {
            glDisable(TT_SCISSOR_TEST);
        }
    }

    bool RenderSystem::isScissorTestEnable() const
    {
        return mScissorTestEnable;
    }

    // Cull Face
    void RenderSystem::setFrontFaceType(const FrontFaceType fft)
    {
        mFrontFaceType = fft;
        glFrontFace(mFrontFaceType);
    }

    FrontFaceType RenderSystem::getFrontFaceType() const
    {
        return mFrontFaceType;
    }

    void RenderSystem::setCullFaceType(const FaceType ft)
    {
        mCullFace = ft;
        glCullFace(mCullFace);
    }

    FaceType RenderSystem::getCullFaceType() const
    {
        return mCullFace;
    }

    void RenderSystem::setCullFaceEnable(const bool b)
    {
        mCullFaceEnable = b;
        if (mCullFaceEnable)
        {
            glEnable(GL_CULL_FACE);
        }
        else
        {
            glDisable(GL_CULL_FACE);
        }
    }

    bool RenderSystem::isCullFaceEnable() const
    {
        return mCullFaceEnable;
    }

    // Polygon Mode
    void RenderSystem::setPolygonMode(const PolygonMode pm)
    {
        mPolygonModeFront = pm;
        mPolygonModeBack = pm;
#if NE_RENDER_SYSTEM == NE_RENDER_SYSTEM_OPENGL
        glPolygonMode(FT_BOTH, pm);
#endif
    }

    PolygonMode RenderSystem::getPolygonMode() const
    {
        return mPolygonModeFront;
    }

    void RenderSystem::setPolygonModeFront(const PolygonMode pm)
    {
        mPolygonModeFront = pm;
#if NE_RENDER_SYSTEM == NE_RENDER_SYSTEM_OPENGL
        glPolygonMode(FT_FRONT, pm);
#endif
    }

    PolygonMode RenderSystem::getPolygonModeFront() const
    {
        return mPolygonModeFront;
    }

    void RenderSystem::setPolygonModeBack(const PolygonMode pm)
    {
        mPolygonModeBack = pm;
#if NE_RENDER_SYSTEM == NE_RENDER_SYSTEM_OPENGL
        glPolygonMode(FT_BACK, pm);
#endif
    }

    PolygonMode RenderSystem::getPolygonModeBack() const
    {
        return mPolygonModeBack;
    }

    // Polygon Offset
    void RenderSystem::setPolygonOffsetPointEnable(const bool b)
    {
        mPolygonOffsetPointEnable = b;
#if NE_RENDER_SYSTEM == NE_RENDER_SYSTEM_OPENGL
        if (mPolygonOffsetPointEnable)
        {
            glEnable(GL_POLYGON_OFFSET_POINT);
        }
        else
        {
            glDisable(GL_POLYGON_OFFSET_POINT);
        }
#endif
    }

    bool RenderSystem::isPolygonOffsetPointEnable() const
    {
        return mPolygonOffsetPointEnable;
    }

    void RenderSystem::setPolygonOffsetLineEnable(const bool b)
    {
        mPolygonOffsetLineEnable = b;
#if NE_RENDER_SYSTEM == NE_RENDER_SYSTEM_OPENGL
        if (mPolygonOffsetLineEnable)
        {
            glEnable(GL_POLYGON_OFFSET_LINE);
        }
        else
        {
            glDisable(GL_POLYGON_OFFSET_LINE);
        }
#endif
    }

    bool RenderSystem::isPolygonOffsetLineEnable() const
    {
        return mPolygonOffsetLineEnable;
    }

    void RenderSystem::setPolygonOffsetFillEnable(const bool b)
    {
        mPolygonOffsetFillEnable = b;
        if (mPolygonOffsetFillEnable)
        {
            glEnable(GL_POLYGON_OFFSET_FILL);
        }
        else
        {
            glDisable(GL_POLYGON_OFFSET_FILL);
        }
    }

    bool RenderSystem::isPolygonOffsetFillEnable() const
    {
        return mPolygonOffsetFillEnable;
    }

    void RenderSystem::setPolygonOffset(const real factor, const real units)
    {
        mPolygonOffsetFactor = factor;
        mPolygonOffsetUnits = units;
        glPolygonOffset(factor, units);
    }

    real RenderSystem::getPolygonOffsetFactor() const
    {
        return mPolygonOffsetFactor;
    }

    real RenderSystem::getPolygonOffsetUnits() const
    {
        return mPolygonOffsetUnits;
    }

    // Blending
    void RenderSystem::setBlendConstantColor(const Color &color)
    {
        mBlendConstantColor = color;
        glBlendColor(mBlendConstantColor.r, mBlendConstantColor.g, mBlendConstantColor.b, mBlendConstantColor.a);
    }

    Color RenderSystem::getBlendConstantColor() const
    {
        return mBlendConstantColor;
    }

    void RenderSystem::setBlendEquation(const BlendEquation equ)
    {
        mBlendColorEquation = equ;
        mBlendAlphaEquation = equ;
        glBlendEquation(mBlendColorEquation);
    }

    BlendEquation RenderSystem::getBlendEquation() const
    {
        return mBlendColorEquation;
    }

    void RenderSystem::setBlendEquationSeparate(const BlendEquation colorEqu, const BlendEquation alphaEqu)
    {
        mBlendColorEquation = colorEqu;
        mBlendAlphaEquation = alphaEqu;
        glBlendEquationSeparate(mBlendColorEquation, mBlendAlphaEquation);
    }

    BlendEquation RenderSystem::getBlendColorEquation() const
    {
        return mBlendColorEquation;
    }

    BlendEquation RenderSystem::getBlendAlphaEquation() const
    {
        return mBlendAlphaEquation;
    }

    void RenderSystem::setBlendFunction(const BlendFunction srcFunc, const BlendFunction dstFunc)
    {
        mBlendSrcColorFunc = srcFunc;
        mBlendDstColorFunc = dstFunc;
        mBlendSrcAlphaFunc = srcFunc;
        mBlendDstAlphaFunc = dstFunc;
        glBlendFunc(mBlendSrcColorFunc, mBlendDstColorFunc);
    }

    BlendFunction RenderSystem::getBlendSrcFunction() const
    {
        return mBlendSrcColorFunc;
    }

    BlendFunction RenderSystem::getBlendDstFunction() const
    {
        return mBlendDstColorFunc;
    }

    void RenderSystem::setBlendFunctionSeparate(const BlendFunction srcColorFunc, const BlendFunction dstColorFunc, const BlendFunction srcAlphaFunc, const BlendFunction dstAlphaFunc)
    {
        mBlendSrcColorFunc = srcColorFunc;
        mBlendDstColorFunc = dstColorFunc;
        mBlendSrcAlphaFunc = srcAlphaFunc;
        mBlendDstAlphaFunc = dstAlphaFunc;
        glBlendFuncSeparate(mBlendSrcColorFunc, mBlendDstColorFunc, mBlendSrcAlphaFunc, mBlendDstAlphaFunc);
    }

    BlendFunction RenderSystem::getBlendSrcColorFunction() const
    {
        return mBlendSrcColorFunc;
    }

    BlendFunction RenderSystem::getBlendDstColorFunction() const
    {
        return mBlendDstColorFunc;
    }

    BlendFunction RenderSystem::getBlendSrcAlphaFunction() const
    {
        return mBlendSrcAlphaFunc;
    }

    BlendFunction RenderSystem::getBlendDstAlphaFunction() const
    {
        return mBlendDstAlphaFunc;
    }

    void RenderSystem::setBlendEnable(const bool b)
    {
        mBlendEnable = b;
        if (mBlendEnable)
        {
            glEnable(GL_BLEND);
        }
        else
        {
            glDisable(GL_BLEND);
        }
    }

    bool RenderSystem::isBlendEnable() const
    {
        return mBlendEnable;
    }

    // Lighting
    void RenderSystem::setLightEnable(const bool b)
    {
        mLightEnable = b;
#if NE_RENDER_SYSTEM == NE_RENDER_SYSTEM_OPENGL
        if (mLightEnable)
        {
            glEnable(GL_LIGHTING);
        }
        else
        {
            glDisable(GL_LIGHTING);
        }
#endif
    }

    bool RenderSystem::isLightEnable() const
    {
        return mLightEnable;
    }

    // Fog
    void RenderSystem::setFog(Fog *pFog)
    {
        mpFog = pFog;
#if NE_RENDER_SYSTEM == NE_RENDER_SYSTEM_OPENGL
        glFogfv(GL_FOG_COLOR, mpFog->getColor().ptr());
        glFogf(GL_FOG_START, mpFog->getStart());
        glFogf(GL_FOG_END, mpFog->getEnd());
        glFogf(GL_FOG_DENSITY, mpFog->getDensity());
        glFogi(GL_FOG_MODE, mpFog->getFogMode());
        glFogi(GL_FOG_COORD_SRC, mpFog->getFogCoordMode());
#endif
    }

    Fog* RenderSystem::getFog() const
    {
        return mpFog;
    }

    void RenderSystem::setFogEnable(const bool b)
    {
        mFogEnable = b;
#if NE_RENDER_SYSTEM == NE_RENDER_SYSTEM_OPENGL
        if (mFogEnable)
        {
            glEnable(GL_FOG);
        }
        else
        {
            glDisable(GL_FOG);
        }
#endif
    }

    bool RenderSystem::isFogEnable() const
    {
        return mFogEnable;
    }

    // Dithering
    void RenderSystem::setDitherEnable(const bool b)
    {
        mDitherEnable = b;
        if (mDitherEnable)
        {
            glEnable(GL_DITHER);
        }
        else
        {
            glDisable(GL_DITHER);
        }
    }

    bool RenderSystem::isDitherEnable() const
    {
        return mDitherEnable;
    }

    // Multisample
    void RenderSystem::setMultisampleEnable(const bool b)
    {
        mMultisampleEnable = b;
#if NE_RENDER_SYSTEM == NE_RENDER_SYSTEM_OPENGL
        if (mMultisampleEnable)
        {
            glEnable(GL_MULTISAMPLE);
        }
        else
        {
            glDisable(GL_MULTISAMPLE);
        }
#endif
    }

    bool RenderSystem::isMultisampleEnable() const
    {
        return mMultisampleEnable;
    }

    bool RenderSystem::setViewport(Viewport *pViewport)
    {
        if (pViewport)
        {
            mpViewport = pViewport;
            Vector2 pos = pViewport->getPosition();
            glViewport(pos.x, pos.y, pViewport->getWidth(), pViewport->getHeight());
            return true;
        }
        return false;
    }

    Viewport* RenderSystem::getViewport() const
    {
        return mpViewport;
    }

    // Material
    void RenderSystem::setMaterial(const MaterialPtr &pMaterial)
    {
        mpMaterialFront = pMaterial;
        mpMaterialBack = pMaterial;
#if NE_RENDER_SYSTEM == NE_RENDER_SYSTEM_OPENGL
        glMaterialfv(FT_BOTH, GL_AMBIENT, pMaterial->getAmbient().ptr());
        glMaterialfv(FT_BOTH, GL_DIFFUSE, pMaterial->getDiffuse().ptr());
        glMaterialfv(FT_BOTH, GL_SPECULAR, pMaterial->getSpecular().ptr());
        glMaterialfv(FT_BOTH, GL_EMISSION, pMaterial->getEmission().ptr());
        glMaterialf(FT_BOTH, GL_SHININESS, pMaterial->getShininess());
#endif
    }

    MaterialPtr RenderSystem::getMaterial() const
    {
        return mpMaterialFront;
    }

    void RenderSystem::setMaterialFront(const MaterialPtr &pMaterial)
    {
        mpMaterialFront = pMaterial;
#if NE_RENDER_SYSTEM == NE_RENDER_SYSTEM_OPENGL
        glMaterialfv(FT_FRONT, GL_AMBIENT, pMaterial->getAmbient().ptr());
        glMaterialfv(FT_FRONT, GL_DIFFUSE, pMaterial->getDiffuse().ptr());
        glMaterialfv(FT_FRONT, GL_SPECULAR, pMaterial->getSpecular().ptr());
        glMaterialfv(FT_FRONT, GL_EMISSION, pMaterial->getEmission().ptr());
        glMaterialf(FT_FRONT, GL_SHININESS, pMaterial->getShininess());
#endif
    }

    MaterialPtr RenderSystem::getMaterialFront() const
    {
        return mpMaterialFront;
    }

    void RenderSystem::setMaterialBack(const MaterialPtr &pMaterial)
    {
        mpMaterialBack = pMaterial;
#if NE_RENDER_SYSTEM == NE_RENDER_SYSTEM_OPENGL
        glMaterialfv(FT_BACK, GL_AMBIENT, pMaterial->getAmbient().ptr());
        glMaterialfv(FT_BACK, GL_DIFFUSE, pMaterial->getDiffuse().ptr());
        glMaterialfv(FT_BACK, GL_SPECULAR, pMaterial->getSpecular().ptr());
        glMaterialfv(FT_BACK, GL_EMISSION, pMaterial->getEmission().ptr());
        glMaterialf(FT_BACK, GL_SHININESS, pMaterial->getShininess());
#endif
    }

    MaterialPtr RenderSystem::getMaterialBack() const
    {
        return mpMaterialBack;
    }

    RenderSystem::~RenderSystem()
    {
    }

    RenderSystem& RenderSystem::GetSingleton()
    {
        assert(mpInstance);
        return *mpInstance;
    }

    RenderSystem* RenderSystem::GetSingletonPtr()
    {
        return mpInstance;
    }
}
